package com.rslai.commons.ssh;

import com.jcraft.jsch.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.TimeoutException;

/**
 * 基于 Jsch 库实现的 ssh 连接客户端
 */
public class Jsch extends AbstractSSH implements SSH {
    private final Log log = LogFactory.getLog(getClass());

    private JSch jsch;
    private Session session;
    private Channel channel;

    private InputStream in; // 输入流，接受服务器返回使用
    private OutputStream out; // 输出流，发送命令使用

    /**
     * 构造行函数
     * @param sshConfig
     */
    public Jsch(SSHConfig sshConfig) throws JSchException, IOException, TimeoutException {
        super(sshConfig);

        this.connect();
    }

    /**
     * 连接ssh服务器
     * @return
     * @throws JSchException
     * @throws IOException
     * @throws TimeoutException
     */
    private SSHResponse connect() throws JSchException, IOException, TimeoutException {
        log.debug(this.toString());

        this.jsch = new JSch();
        session = jsch.getSession(this.sshConfig.getUsername(), this.sshConfig.getHost(), this.sshConfig.getPort());
        session.setPassword(this.sshConfig.getPassword());
        session.setConfig("StrictHostKeyChecking", this.sshConfig.getStrictHostKeyChecking().name());
        session.connect(this.sshConfig.getConnectTimeout());
        channel = session.openChannel("shell");
        channel.setInputStream(null);

        this.in = channel.getInputStream();
        this.out = channel.getOutputStream();
        // ((ChannelExec) channel).setErrStream(System.err); // 错误输出流暂时不知道什么时候有用

        channel.connect();

        SSHResponse sshResponse = readResponse();
        sshInLog(sshResponse.getBody());
        return sshResponse;
    }

    @Override
    public SSHResponse execute(String command) throws IOException, JSchException, TimeoutException {
        super.execute(command);
        if ((in == null) || (out == null)) {
            throw new JSchException("ssh 连接已断开");
        }

        sshOutLog(command);
        out.write(util.str2byte(command + "\r", this.sshConfig.getCharset()));
        out.flush();

        SSHResponse sshResponse = readResponse();
        sshInLog(sshResponse.getBody());
        return sshResponse;
    }

    @Override
    public void disconnect() {
        try {
            if (in != null) {
                in.close();
            }
        } catch (Exception e) {
        }
        try {
            if (out != null) {
                out.close();
            }
        } catch (Exception e) {
        }
        try {
            if (channel.isConnected()) {
                channel.disconnect();
            }
        } catch (Exception e) {
        }
        try {
            if (session.isConnected()) {
                session.disconnect();
            }
        } catch (Exception e) {
        }
    }

    /**
     * 读取返回数据
     * @return
     * @throws IOException
     * @throws TimeoutException
     */
    private SSHResponse readResponse() throws IOException, TimeoutException {
        long endTime = System.currentTimeMillis() + this.sshConfig.getReadTimeout();

        SSHResponse sshResponse = new JschSSHResponse(this.sshConfig, this.isConnect);

        byte[] tmpBytes = new byte[2048];
        while (true) {
            if (in.available() > 0) {
                int length = in.read(tmpBytes, 0, 2048);
                sshResponse.addResponse(tmpBytes, length);

                if (sshResponse.isDone()) {
                    break;
                }
            }

            try {
                Thread.sleep(20);
            } catch (Exception e) {
            }

            // 读取ssh返回数据超时
            if (endTime <= System.currentTimeMillis()) {
                throw new TimeoutException("读取ssh返回数据超时 timeout:" + (this.sshConfig.getReadTimeout()/1000) + "s");
            }
        }

        return sshResponse;
    }

}
